<template>
  <div id="virtual-list-no-height" ref="warp" @scroll="handScroll">
    <div ref="scrollHeight"></div>
    <div class="visibleWarp" :style="{ transform: `translateY(${offset}px)` }">
      <div v-for="item in visibleList" :key="item.id" :id="item.id" ref="items">
        <slot :item="item"></slot>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  name: "virtual-list-no-height",
  props: {
    size: Number,
    remain: Number,
    dataList: Array
  },
  data() {
    return {
      start: 0,
      end: this.remain,
      offset: 0
    };
  },
  computed: {
    visibleList() {
      // 防止前面渲染空白 预留部分数据
      const prveCount = Math.min(this.start, this.remain);
      console.log(prveCount, "prveCount1");
      const reallyStart = this.start - prveCount;
      // 防止后面渲染空白 预留部分数据
      const nextCount = Math.min(this.dataList.length - this.end, this.remain);
      const reallyEnd = this.end + nextCount;
      return this.dataList.slice(reallyStart, reallyEnd);
    }
  },
  mounted() {
    // 计算全局区域的高度
    this.$refs.scrollHeight.style.height =
      this.size * this.dataList.length - 1 + "px";
    // 计算可视区域的高度
    this.$refs.warp.style.height = this.remain * this.size + "px";
    // 缓存全部数据的高度
    this.cacheList();
  },
  updated() {
    // dom 更新后开始计算
    this.$nextTick(() => {
      const dom = this.$refs.items;
      if (!(dom && dom.length > 0)) return; //没有dom 就返回
      dom.forEach((item) => {
        const { height } = item.getBoundingClientRect(); // 获取每个节点的真实dom 高度
        // 更新缓存里面的数据
        const id = item.getAttribute("id"); // 拿到节点id
        const oldHeight = this.positionData[id].height; // 获取缓存数据高度
        const diffence = oldHeight - height;
        if (diffence) {
          //如果高度改变 更新bottom height  top不需要改动
          this.positionData[id].height = height;
          this.positionData[id].bottom =
            this.positionData[id].bottom - diffence;
          // 后面的 top   bottom 都需要改变
          for (let i = id + 1; i < this.positionData.length; i++) {
            if (this.positionData[i]) {
              this.positionData[i].top = this.positionData[i - 1].bottom;
              this.positionData[i].bottom =
                this.positionData[i - 1].bottom - diffence;
            }
          }
        }
      });

      this.$refs.scrollHeight.style.height =
        this.positionData[this.positionData.length - 1].bottom + "px";
      console.log(this.$refs.scrollHeight.style.height);
    });
  },
  methods: {
    // 缓存数据
    cacheList() {
      // 保存数组中每一项的高度  top  和 bottom
      this.positionData = this.dataList.map((item, index) => {
        return {
          height: this.size,
          top: this.size * index,
          bottom: (index + 1) * this.size
        };
      });
    },
    handScroll() {
      // 获取滚动条的滚动的距离 以为每一项的高度是不定 所以计算方式要改变
      const prveCount = Math.min(this.start, this.remain);
      const scrollTop = this.$refs.warp.scrollTop;
      //   this.start =
      //     Math.floor(scrollTop / this.size) - 1 >= 0
      //       ? Math.floor(scrollTop / this.size)
      //       : 0;
      //   this.end = this.start + this.remain;
      //   // 计算列表移动的偏移值
      //   this.offset = this.start * this.size;
      this.start = this.getStartIndex(scrollTop);
      this.end = this.start + this.remain;
      this.offset = this.positionData[this.start - prveCount]
        ? this.positionData[this.start - prveCount].top
        : 0;
    },
    getStartIndex(scrollTop) {
      // 二分查找 移动距离是 哪一项 的bottom
      let start = 0; // 第一个选项
      let end = this.positionData.length - 1; // 最后一个选项
      let temp = null;
      while (start <= end) {
        let midIndex = (start + end) >> 1;
        // 中间一项的bootom 值
        let midVal = this.positionData[midIndex].bottom;
        if (scrollTop === midVal) {
          return midIndex + 1;
        } else if (scrollTop < midVal) {
          end = midIndex - 1;
          if (temp === null || temp > midIndex) {
            temp = midIndex;
          }
        } else if (scrollTop > midVal) {
          start = midIndex + 1;
          if (temp === null || temp < midIndex) {
            temp = midIndex;
          }
        }
      }
      return temp;
    }
  }
};
</script>

<style lang="scss" scoped>
#virtual-list-no-height {
  position: relative;
  overflow-y: auto;
}
.visibleWarp {
  position: absolute;
  left: 0;
  top: 0;
  width: 100%;
  z-index: 2;
}
</style>
